using System;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Security.AccessControl;
using System.Xml.Serialization;
using Splicer.Renderer;
using Splicer.Timeline;

namespace ArchiveLink
{
    public static class EnumerationExtensions
    {

        public static bool Has<T>(this System.Enum type, T value)
        {
            try
            {
                return (((int)(object)type & (int)(object)value) == (int)(object)value);
            }
            catch
            {
                return false;
            }
        }

        public static bool Is<T>(this System.Enum type, T value)
        {
            try
            {
                return (int)(object)type == (int)(object)value;
            }
            catch
            {
                return false;
            }
        }


        public static T Add<T>(this System.Enum type, T value)
        {
            try
            {
                return (T)(object)(((int)(object)type | (int)(object)value));
            }
            catch (Exception ex)
            {
                throw new ArgumentException(
                    string.Format(
                        "Could not append value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }
        }


        public static T Remove<T>(this System.Enum type, T value)
        {
            try
            {
                return (T)(object)(((int)(object)type & ~(int)(object)value));
            }
            catch (Exception ex)
            {
                throw new ArgumentException(
                    string.Format(
                        "Could not remove value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }
        }

    } 


    public class LinkedDrive
    {
        private readonly DriveWatcher _driveWatcher;
        private readonly Configuration _config;
        private readonly ILogger _logger;
        private FileSystemWatcher _fsw;
        private SyncerFactory _syncerFactory;
        public string Label { get; private set; }
        public bool IsConnected { get { return _driveWatcher.IsConnected(Label); } }

        public LinkedDrive(string label,DriveWatcher driveWatcher, Configuration config, ILogger logger)
        {
            if (string.IsNullOrWhiteSpace(label))
            {
                throw new ArgumentException();
            }
            _driveWatcher = driveWatcher;
            _config = config;
            _logger = logger;
            _driveWatcher.DriveRemoved += new DriveWatcher.DriveArrivedHandler(_driveWatcher_DriveRemoved);
            _driveWatcher.DriveArrived += new DriveWatcher.DriveArrivedHandler(_driveWatcher_DriveArrived);
            Label = label;

            FileInfo mockFile = MockVideo.GetMockFile(Label, _config);
            _syncerFactory = new SyncerFactory(mockFile);

        }

        void _driveWatcher_DriveArrived(object sender, DriveEventArgs e)
        {

            if (e.Name == Label)
            {
                _logger.WriteEntry("connected drive: " + e.Drive + " " + e.Name + " " + e.Serial);
                HandleArrivedDrive(e);
            }
        }

        private void HandleArrivedDrive(DriveEventArgs e)
        {
            var mirrorDir = new DirectoryInfo(Path.Combine(_config.GetOfflineDir(), Label));
            var linkDir = new DirectoryInfo(Path.Combine(_config.MirrorPath, Label));
            if (mirrorDir.Exists)
            {
                MakeRW(mirrorDir);
            }
            if (linkDir.Exists)
                {
                    linkDir.Delete();
                }
                
                DirectoryLinker.CreateMountPoint(new DirectoryInfo(e.Drive).FullName, linkDir.FullName);
                InitFSWatcher(linkDir);
                UpdateDrive(e);
            
        }


        private void InitFSWatcher(DirectoryInfo linkDir)
        {
            StopFSWatcher();
            _fsw = new FileSystemWatcher(linkDir.FullName);
            _fsw.IncludeSubdirectories = true;
            _fsw.Created += new FileSystemEventHandler(_fsw_Created);
            _fsw.Deleted += new FileSystemEventHandler(_fsw_Deleted);
            _fsw.Renamed += new RenamedEventHandler(_fsw_Renamed);
            _fsw.Changed += new FileSystemEventHandler(_fsw_Changed);
            _fsw.EnableRaisingEvents = true;
        }

        void _fsw_Changed(object sender, FileSystemEventArgs e)
        {
            var mirrorPath = GetMirrorPath((FileSystemWatcher)sender, e.FullPath);
            try
            {
                DeleteMirror(mirrorPath);
            }
            catch (Exception)
            {
            }
            try
            {
                CreateMirror(e, mirrorPath);
            }
            catch (Exception)
            {
            } 
        }

        void _fsw_Renamed(object sender, RenamedEventArgs e)
        {
            var mirrorPath = GetMirrorPath((FileSystemWatcher)sender, e.FullPath);
            var oldPath = GetMirrorPath((FileSystemWatcher)sender, e.OldFullPath);
            try
            {
                File.Move(oldPath,mirrorPath);
            }
            catch (Exception)
            {
            }
        }

        void _fsw_Deleted(object sender, FileSystemEventArgs e)
        {
            var mirrorPath = GetMirrorPath((FileSystemWatcher)sender, e.FullPath);
            try
            {
                DeleteMirror(mirrorPath);
            }
            catch (Exception)
            {
            }
        }

        private static void DeleteMirror(string mirrorPath)
        {
            var fi = new FileInfo(mirrorPath);
            if (fi.Attributes.HasFlag(FileAttributes.Directory))
            {
                Directory.Delete(mirrorPath);
            }
            else
            {
                File.Delete(mirrorPath);
            }
        }

        void _fsw_Created(object sender, FileSystemEventArgs e)
        {
            var mirrorPath = GetMirrorPath((FileSystemWatcher)sender, e.FullPath);
            try
            {
                CreateMirror(e, mirrorPath);
            }
            catch (Exception)
            {
            } 
        }

        private void CreateMirror(FileSystemEventArgs e, string mirrorPath)
        {
            var fi = new FileInfo(e.FullPath);
            if (fi.Attributes.HasFlag(FileAttributes.Directory))
            {
                var di = new DirectoryInfo(mirrorPath);
                if (!di.Exists)
                {
                    di.Create();
                }
            }
            else
            {
                var syncer = _syncerFactory.GetSyncerImpl(e.FullPath);
                syncer.CreateMirror(mirrorPath);
                //DirectoryLinker.CreateFileLink(mockFile, mirrorPath);
            }
        }

        private string GetMirrorPath(FileSystemWatcher fsw, string path)
        {
            var mirrorDir = new DirectoryInfo(Path.Combine(_config.GetOfflineDir(), Label));

            var affectedFile = new FileInfo(path);
            var relativePath = affectedFile.FullName.Substring(fsw.Path.Length,affectedFile.FullName.Length - fsw.Path.Length).Trim('\\');
            var mirrorPath = Path.Combine(mirrorDir.FullName, relativePath);
            return mirrorPath;
        }

        private void StopFSWatcher()
        {
            var fsw = _fsw;
            _fsw = null;
            if (fsw != null)
            {
                fsw.EnableRaisingEvents = false;
                fsw.Dispose();
            }
        }


        private void UpdateDrive(DriveEventArgs e)
        {
            var drive = new DriveInfo(e.Drive);
            var archiveDir = new DirectoryInfo(_config.GetOfflineDir());
            FileInfo mockFile = MockVideo.GetMockFile(e.Name,_config);

            var syncer = new DirectorySyncer(drive, e, archiveDir, mockFile);
            syncer.DoLink();
            syncer.DoClean();
        }
        
        void _driveWatcher_DriveRemoved(object sender, DriveEventArgs e)
        {

            if (e.Name==Label)
            {
                _logger.WriteEntry("disconnected drive: " + e.Drive + " " + e.Name + " " + e.Serial);
                HandleRemovedDrive();
            }
        }

        private void HandleRemovedDrive()
        {
            StopFSWatcher();

            var mirrorDir = new DirectoryInfo(Path.Combine(_config.GetOfflineDir(), Label));
            if (!mirrorDir.Exists)
            {
                mirrorDir.Create();
            }
            var linkDir = new DirectoryInfo(Path.Combine(_config.MirrorPath, Label));
                //DeleteMountPoint(linkDir.FullName);
                DirectoryLinker.CreateDirLink(mirrorDir, linkDir.FullName);

                MakeRO(mirrorDir);
        }


        private void MakeRW(DirectoryInfo mirrorDir)
        {

            var acl = mirrorDir.GetAccessControl();
            
            var rules = acl.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount));
            foreach (var rule in rules)
            {
                var fsRule = rule as FileSystemAccessRule;
                if (fsRule != null)
                {
                    if (fsRule.AccessControlType == AccessControlType.Deny){
                        acl.RemoveAccessRuleSpecific(fsRule);
                    }
                }
            }

            mirrorDir.SetAccessControl(acl);
        }

        private static void MakeRO(DirectoryInfo mirrorDir)
        {
            var usr = System.Security.Principal.WindowsIdentity.GetCurrent(false);

            var acl = mirrorDir.GetAccessControl();
            var rules = acl.GetAccessRules(true, true, typeof (System.Security.Principal.NTAccount));
            foreach (var rule in rules)
            {
                var fsRule = rule as FileSystemAccessRule;
                if (fsRule != null)
                {
                    if (fsRule.IdentityReference == usr.User)
                    {
                    }
                    else
                    {
                        var newrules =
                            fsRule.FileSystemRights.Remove(FileSystemRights.Write).Remove(FileSystemRights.Delete).Remove(
                                FileSystemRights.DeleteSubdirectoriesAndFiles);
                        var newrules2 =
                            FileSystemRights.Write.Add(FileSystemRights.Delete).Add(
                                FileSystemRights.DeleteSubdirectoriesAndFiles);
                        var newrule = new FileSystemAccessRule(fsRule.IdentityReference, newrules2,
                                                               AccessControlType.Deny);
                        //acl.RemoveAccessRuleAll(newrule);
                        acl.SetAccessRule(newrule);
                    }
                }
            }

            mirrorDir.SetAccessControl(acl);
        }

        public void Sync()
        {
            if (IsConnected)
            {
                var drive = _driveWatcher.GetConnectedInfo(Label);
                HandleArrivedDrive(drive);
            }
            else
            {
                HandleRemovedDrive();
            }
        }
    }

    public interface ILogger
    {
        void WriteEntry(string message);
    }
}